# -*- coding: utf-8 -*-

import re
import os

from typing import Dict
from typing import List
from conf.entity.module.grpc_config import GrpcConfig, GrpcAndDomainConverterConfig
from com.maycur.grpc.base.base_entity import JavaMethodBuilder, java_utils_dict
from com.maycur.grpc.base.base_tools import append_import_to_line, enrich_nested_converter
from com.maycur.grpc.base.grpc_entity import GrpcFullMapping, GrpcMessage, GrpcField
from com.maycur.grpc.base.java_loader import JavaClassInfo, JavaFieldInfo, JavaLoader, JavaMethodInfo
from com.maycur.utils import CommonUtils, JavaAstUtils


class GrpcAndJavaConverter(object):
    config: GrpcConfig
    converter_config: GrpcAndDomainConverterConfig
    converters: List[JavaClassInfo]
    domain_loader: JavaLoader
    converter_loader: JavaLoader
    method_converter_dict: Dict[str, JavaClassInfo]
    mapping: GrpcFullMapping
    result: bool

    def __init__(self, config: GrpcConfig, mapping: GrpcFullMapping):
        self.config = config
        self.converter_config = config.converter_config
        self.mapping = mapping
        self.result = True

        # default struct init
        self.domain_loader = JavaLoader()
        self.converter_loader = JavaLoader()
        self.converters = []
        self.method_converter_dict = {}

    def _load_all_domains(self, domain_path):
        self.domain_loader.load_by_package(domain_path)
        self.domain_loader.load_import_references(self.converter_config)

    def _load_all_converters(self, converter_path):
        self.converter_loader.load_by_package(converter_path)
        for c_item in self.converter_loader.classes:
            for m_item in c_item.methods:
                self.method_converter_dict[m_item.name] = c_item
        print('previous converters loaded successfully')

    def _get_optimized_domain_getter(self, domain_field: JavaFieldInfo, domain_var: str, grpc: GrpcMessage):
        """
        get domain getter
        :param domain_field: the domain field
        :param domain_var: the declared domain variable
        :return: optimized domain getter, with brackets and in case of NullPointerException
        """
        grpc_f = self._get_grpc_field_by_domain_f_name(domain_field.name, grpc)
        domain_getter = f'{domain_var}.get' + CommonUtils.upper_first(domain_field.name) + '()'
        if domain_field.type in self.domain_loader.enum_dict:
            return f'{domain_getter}.name()'
        elif domain_field.type == 'MkJSONObject':
            return f'Objects.nonNull({domain_getter}) ? {domain_getter}.toJSONString() : StringUtils.EMPTY'
        elif domain_field.type == 'String':
            return f'StringUtils.trimToEmpty({domain_getter})'
        elif domain_field.type == 'boolean':
            if domain_field.name.lower().startswith('is'):
                return f'{domain_var}.{domain_field.name}()'
            return f'{domain_var}.is{CommonUtils.upper_first(domain_field.name)}()'
        elif domain_field.type == 'Date':
            return f'Objects.nonNull({domain_getter}) ? {domain_getter}.getTime() : 0L'
        elif domain_field.type == 'BigDecimal':
            return f'format.format({domain_getter})'
        elif grpc_f is not None and grpc_f.f_type == 'string' and domain_field.type != 'String':
            return f'JSON.toJSONString({domain_getter})'
        return domain_getter

    @staticmethod
    def _wrap_grpc_date_getter(convert_class: JavaClassInfo, domain_f: JavaFieldInfo, grpc_var: str,
                               import_append_idx: int) -> str:
        append_import_to_line(convert_class, java_utils_dict['Date'], import_append_idx)
        grpc_getter_name = 'get' + CommonUtils.upper_first(domain_f.name)
        return f'new Date({grpc_var}.{grpc_getter_name}())'

    def _append_enum_import_to_line(self, convert_class: JavaClassInfo, enum_type: str, append_line_idx: int):
        enum_class = self.domain_loader.enum_dict[enum_type]
        import_path = f'{enum_class.package}.{enum_class.simple_name}'
        append_import_to_line(convert_class, import_path, append_line_idx)

    def _append_mk_json_import_to_line(self, convert_class: JavaClassInfo, append_line_idx: int):
        if 'MkJSONObject' not in self.domain_loader.simple_name_dict:
            raise KeyError('MkJSONObject is not used in domain')
        mk_json = self.domain_loader.simple_name_dict['MkJSONObject']
        import_path = f'{mk_json.package}.{mk_json.simple_name}'
        append_import_to_line(convert_class, import_path, append_line_idx)

    def _append_domain_f_import_to_line(self, convert_class: JavaClassInfo, append_line_idx: int, domain_f_type: str):
        if domain_f_type in self.domain_loader.simple_name_dict:
            domain_f = self.domain_loader.simple_name_dict[domain_f_type]
            import_path = f'{domain_f.package}.{domain_f.simple_name}'
            append_import_to_line(convert_class, import_path, append_line_idx)

    def _domain_field_in_grpc(self, domain_f: str, grpc: GrpcMessage):
        return self._get_grpc_field_by_domain_f_name(domain_f, grpc) is not None

    @staticmethod
    def _get_grpc_field_by_domain_f_name(domain_f: str, grpc: GrpcMessage) -> GrpcField:
        for item in grpc.fields:
            if domain_f == item.name:
                return item

    def _convert_domain_2_grpc(self, convert_class: JavaClassInfo,
                               domain: JavaClassInfo, grpc: GrpcMessage) -> bool:
        grpc_package = self.config.proto_config.java_package
        grpc_type = grpc.get_optimized_name()
        domain_type = domain.simple_name
        domain_var = CommonUtils.lower_first(domain_type)

        # pre-check
        convert_method_name = f'new{grpc_type}'
        if convert_method_name in convert_class.method_dict:
            print(f'{convert_method_name} already defined in {convert_class.simple_name}')
            return False

        # the line under package
        import_append_idx = 1

        new_grpc_m = JavaMethodBuilder(4)
        new_grpc_m.add_line(f'public {grpc_type} {convert_method_name}({domain_type} {domain_var})' + ' {', 1)

        # method body
        g_builder_var = 'resBuilder'
        new_grpc_m.add_line(f'{grpc_type}.Builder {g_builder_var} = {grpc_type}.newBuilder();', 2)
        # check whether domain_var is null
        new_grpc_m.add_line(f'if (Objects.isNull({domain_var}))' + ' {', 2)
        new_grpc_m.add_line(f'return null;', 3)
        new_grpc_m.add_line('}', 2)
        # an empty line
        new_grpc_m.add_line('', 2)
        # core gRPC builder
        if domain.has_big_decimal:
            new_grpc_m.add_line('DecimalFormat format = new DecimalFormat("0.00");', 2)
            append_import_to_line(convert_class, java_utils_dict['DecimalFormat'], import_append_idx)
            append_import_to_line(convert_class, java_utils_dict['BigDecimal'], import_append_idx)
        for domain_f in domain.fields:
            if self._domain_field_in_grpc(domain_f.name, grpc):
                grpc_setter_name = 'set' + CommonUtils.upper_first(domain_f.name)
                domain_getter = self._get_optimized_domain_getter(domain_f, domain_var, grpc)
                grpc_f = self._get_grpc_field_by_domain_f_name(domain_f.name, grpc)
                if domain_f.type == 'List':
                    grpc_f_type = self._convert_list_argument_type(convert_class, domain_f.list_type, grpc_f)
                    domain_getter_with_optional = f'Optional.ofNullable({domain_getter}).orElse(Lists.newArrayList())'
                    new_grpc_m.add_line(f'{domain_getter_with_optional}.forEach(domainItem ->' + ' {', 2)
                    if grpc_f_type in self.mapping.grpc.basic_proto_type_set:
                        grpc_m = 'domainItem'
                    else:
                        grpc_m = f'new{grpc_f_type}(domainItem)'
                    new_grpc_m.add_line(f'resBuilder.add{CommonUtils.upper_first(domain_f.name)}({grpc_m});', 3)
                    new_grpc_m.add_line('});', 2)

                    append_import_to_line(convert_class, java_utils_dict['Optional'], import_append_idx)

                # Non-String and String
                elif grpc_f.f_type == 'string' and domain_f.type != 'String':
                    append_import_to_line(convert_class, java_utils_dict['JSON'], import_append_idx)
                    self._append_domain_f_import_to_line(convert_class, import_append_idx, domain_f.type)
                    new_grpc_m.add_line(f'{g_builder_var}.{grpc_setter_name}(JSON.toJSONString({domain_getter}));', 2)

                elif domain_f.type in self.domain_loader.simple_name_dict \
                        and domain_f.type not in self.domain_loader.enum_dict \
                        and domain_f.type != 'MkJSONObject':
                    self._do_convert_java_and_grpc_by_domain_field(domain_f)
                    grpc_new_type = 'new' + self.config.proto_config.message_prefix + domain_f.type
                    wrapped_domain_getter = ''
                    if grpc_new_type in self.method_converter_dict:
                        field_converter = self.method_converter_dict[grpc_new_type]
                        lower_f_converter_name = CommonUtils.lower_first(field_converter.simple_name)
                        if CommonUtils.lower_first(convert_class.simple_name) != lower_f_converter_name:
                            wrapped_domain_getter = f'{lower_f_converter_name}.'
                            enrich_nested_converter(convert_class, field_converter)
                        wrapped_domain_getter += f'{grpc_new_type}({domain_getter})'
                    new_grpc_m.add_line(f'{g_builder_var}.{grpc_setter_name}({wrapped_domain_getter});', 2)
                else:
                    new_grpc_m.add_line(f'{g_builder_var}.{grpc_setter_name}({domain_getter});', 2)
        new_grpc_m.add_line(f'return {g_builder_var}.build();', 2)

        new_grpc_m.add_line('}', 1)

        # the line before last '}'
        lines_len = len(convert_class.lines)
        method_append_idx = lines_len - 1
        for i in range(0, lines_len):
            if '}' in convert_class.lines[lines_len - 1 - i]:
                method_append_idx = lines_len - 1 - i - 1
                break

        # collect imports
        # Utils
        append_import_to_line(convert_class, java_utils_dict['Objects'], import_append_idx)
        append_import_to_line(convert_class, java_utils_dict['StringUtils'], import_append_idx)
        append_import_to_line(convert_class, java_utils_dict['List'], import_append_idx)
        append_import_to_line(convert_class, java_utils_dict['Lists'], import_append_idx)
        # domain and gRPC
        append_import_to_line(convert_class, f'{domain.package}.{domain.simple_name}', import_append_idx)
        append_import_to_line(convert_class, f'{grpc_package}.{grpc_type}', import_append_idx)

        # map method_dict with empty JavaMethodInfo, in order to check later
        convert_class.method_dict[convert_method_name] = JavaMethodInfo()

        # append the generated method
        convert_class.lines[method_append_idx] += '\n' + new_grpc_m.build()

        # map convert method with the converter
        self.method_converter_dict[convert_method_name] = convert_class
        return True

    def _convert_grpc_2_domain(self, convert_class: JavaClassInfo,
                               grpc: GrpcMessage,
                               domain: JavaClassInfo) -> bool:
        grpc_package = self.config.proto_config.java_package
        grpc_type = grpc.get_optimized_name()
        domain_type = domain.simple_name
        grpc_var = 'grpc'

        # pre-check
        convert_method_name = f'from{grpc.get_optimized_name()}'
        if convert_method_name in convert_class.method_dict:
            print(f'{convert_method_name} already defined in {convert_class.simple_name}')
            return False

        # the line under package
        import_append_idx = 1

        from_grpc_m = JavaMethodBuilder(4)
        from_grpc_m.add_line(f'public {domain_type} {convert_method_name}({grpc_type} {grpc_var})' + ' {', 1)

        # method body
        # check whether grpc_var is empty
        from_grpc_m.add_line(f'if (Objects.isNull({grpc_var}))' + ' {', 2)
        from_grpc_m.add_line('return null;', 3)
        from_grpc_m.add_line('}', 2)
        # an empty line
        from_grpc_m.add_line('', 2)
        # core domain builder
        if domain.has_lombok_builder:
            from_grpc_m.add_line(f'return {domain_type}.builder()', 2)
            for domain_f in domain.fields:
                if self._domain_field_in_grpc(domain_f.name, grpc):
                    grpc_f = self._get_grpc_field_by_domain_f_name(domain_f.name, grpc)
                    grpc_getter_name = 'get' + CommonUtils.upper_first(domain_f.name)
                    grpc_getter = f'{grpc_var}.{grpc_getter_name}()'

                    # List type
                    if domain_f.type == 'List':
                        grpc_f_type = self._convert_list_argument_type(convert_class, domain_f.list_type, grpc_f)
                        self._gen_from_grpc_list_field_code(from_grpc_m, domain_f, grpc_f_type,
                                                            convert_class, import_append_idx, 4, True)
                    # enum type
                    elif domain_f.type in self.domain_loader.enum_dict:
                        self._append_enum_import_to_line(convert_class, domain_f.type, import_append_idx)
                        enum_line = f'.{domain_f.name}({domain_f.type}.valueOf({grpc_getter}))'
                        from_grpc_m.add_line(enum_line, 4)

                    # MkJSONObject
                    elif domain_f.type == 'MkJSONObject':
                        self._append_mk_json_import_to_line(convert_class, import_append_idx)
                        mk_json_line = f'.{domain_f.name}(MkJSONObject.stringToJSONObject({grpc_getter}))'
                        from_grpc_m.add_line(mk_json_line, 4)

                    # BigDecimal type
                    elif domain_f.type == 'BigDecimal':
                        decimal_line = f'.{domain_f.name}(new BigDecimal({grpc_getter}))'
                        from_grpc_m.add_line(decimal_line, 4)

                    # Date type
                    elif domain_f.type == 'Date':
                        date_getter = self._wrap_grpc_date_getter(convert_class, domain_f, grpc_var, import_append_idx)
                        lombok_date_builder = f'.{domain_f.name}({date_getter})'
                        from_grpc_m.add_line(lombok_date_builder, 4)

                    # Non-String and String
                    elif grpc_f.f_type == 'string' and domain_f.type != 'String':
                        append_import_to_line(convert_class, java_utils_dict['JSON'], import_append_idx)
                        self._append_domain_f_import_to_line(convert_class, import_append_idx, domain_f.type)
                        json_obj_line = f'.{domain_f.name}(JSON.parseObject({grpc_getter}, {domain_f.type}.class))'
                        from_grpc_m.add_line(json_obj_line, 4)

                    # combined type
                    elif domain_f.type in self.domain_loader.simple_name_dict:
                        self._do_convert_java_and_grpc_by_domain_field(domain_f)
                        grpc_from_type = 'from' + self.config.proto_config.message_prefix + domain_f.type
                        if grpc_from_type in self.method_converter_dict:
                            field_converter = self.method_converter_dict[grpc_from_type]
                            lower_f_converter_name = CommonUtils.lower_first(field_converter.simple_name)
                            wrapped_grpc_getter = ''
                            if CommonUtils.lower_first(convert_class.simple_name) != lower_f_converter_name:
                                wrapped_grpc_getter = f'{lower_f_converter_name}.'
                                enrich_nested_converter(convert_class, field_converter)
                            wrapped_grpc_getter += f'{grpc_from_type}({grpc_getter})'
                            from_grpc_m.add_line(f'.{domain_f.name}({wrapped_grpc_getter})', 4)
                    # Common type
                    else:
                        from_grpc_m.add_line(f'.{domain_f.name}({grpc_getter})', 4)
            from_grpc_m.add_line('.build();', 4)
        else:
            domain_var = 'domain'
            from_grpc_m.add_line(f'{domain_type} {domain_var} = new {domain_type}();', 2)
            for domain_f in domain.fields:
                if self._domain_field_in_grpc(domain_f.name, grpc):
                    grpc_f = self._get_grpc_field_by_domain_f_name(domain_f.name, grpc)
                    domain_setter_name = 'set' + CommonUtils.upper_first(domain_f.name)
                    domain_setter = f'{domain_var}.{domain_setter_name}'
                    grpc_getter_name = 'get' + CommonUtils.upper_first(domain_f.name)
                    grpc_getter = f'{grpc_var}.{grpc_getter_name}()'

                    # List type
                    if domain_f.type == 'List':
                        grpc_f_type = self._convert_list_argument_type(convert_class, domain_f.list_type, grpc_f)
                        self._gen_from_grpc_list_field_code(from_grpc_m, domain_f, grpc_f_type,
                                                            convert_class, import_append_idx, 2, False)
                    # enum type
                    elif domain_f.type in self.domain_loader.enum_dict:
                        self._append_enum_import_to_line(convert_class, domain_f.type, import_append_idx)
                        enum_line = f'{domain_setter}({domain_f.type}.valueOf({grpc_getter}));'
                        from_grpc_m.add_line(enum_line, 2)

                    # MkJSONObject
                    elif domain_f.type == 'MkJSONObject':
                        self._append_mk_json_import_to_line(convert_class, import_append_idx)
                        json_line = f'{domain_setter}(MkJSONObject.stringToJSONObject({grpc_getter}));'
                        from_grpc_m.add_line(json_line, 2)

                    # BigDecimal type
                    elif domain_f.type == 'BigDecimal':
                        decimal_line = f'{domain_setter}(new BigDecimal({grpc_getter}));'
                        from_grpc_m.add_line(decimal_line, 2)

                    # Date type
                    elif domain_f.type == 'Date':
                        g_getter = self._wrap_grpc_date_getter(convert_class, domain_f, grpc_var, import_append_idx)
                        from_grpc_m.add_line(f'{domain_setter}({g_getter});', 2)

                    # boolean type
                    elif domain_f.type == 'boolean':
                        if domain.has_lombok_data and domain_f.name.lower().startswith('is'):
                            domain_setter_name = 'set' + CommonUtils.upper_first(domain_f.name[2:])
                            domain_setter = f'{domain_var}.{domain_setter_name}'
                        
                        from_grpc_m.add_line(f'{domain_setter}({grpc_getter});', 2)

                    # Non-String and String
                    elif grpc_f.f_type == 'string' and domain_f.type != 'String':
                        append_import_to_line(convert_class, java_utils_dict['JSON'], import_append_idx)
                        self._append_domain_f_import_to_line(convert_class, import_append_idx, domain_f.type)
                        json_obj_line = f'{domain_setter}(JSON.parseObject({grpc_getter}, {domain_f.type}.class));'
                        from_grpc_m.add_line(json_obj_line, 2)

                    # combined type
                    elif domain_f.type in self.domain_loader.simple_name_dict:
                        self._do_convert_java_and_grpc_by_domain_field(domain_f)
                        grpc_from_type = 'from' + self.config.proto_config.message_prefix + domain_f.type
                        if grpc_from_type in self.method_converter_dict:
                            field_converter = self.method_converter_dict[grpc_from_type]
                            lower_f_converter_name = CommonUtils.lower_first(field_converter.simple_name)
                            wrapped_grpc_getter = ''
                            if CommonUtils.lower_first(convert_class.simple_name) != lower_f_converter_name:
                                wrapped_grpc_getter = f'{lower_f_converter_name}.'
                                enrich_nested_converter(convert_class, field_converter)
                            wrapped_grpc_getter += f'{grpc_from_type}({grpc_getter})'
                            from_grpc_m.add_line(f'{domain_setter}({wrapped_grpc_getter});', 2)
                    # Common type
                    else:
                        from_grpc_m.add_line(f'{domain_setter}({grpc_var}.{grpc_getter_name}());', 2)
            # an empty line
            from_grpc_m.add_line('', 2)
            from_grpc_m.add_line(f'return {domain_var};', 2)

        from_grpc_m.add_line('}', 1)

        # the line before last '}'
        lines_len = len(convert_class.lines)
        method_append_idx = lines_len - 1
        for i in range(0, lines_len):
            if '}' in convert_class.lines[lines_len - 1 - i]:
                method_append_idx = lines_len - 1 - i - 1
                break

        # collect imports
        # Utils
        append_import_to_line(convert_class, java_utils_dict['Objects'], import_append_idx)
        append_import_to_line(convert_class, java_utils_dict['StringUtils'], import_append_idx)
        append_import_to_line(convert_class, java_utils_dict['List'], import_append_idx)
        append_import_to_line(convert_class, java_utils_dict['Lists'], import_append_idx)
        # domain and gRPC
        append_import_to_line(convert_class, f'{domain.package}.{domain.simple_name}', import_append_idx)
        append_import_to_line(convert_class, f'{grpc_package}.{grpc_type}', import_append_idx)

        # map method_dict with empty JavaMethodInfo, in order to check later
        convert_class.method_dict[convert_method_name] = JavaMethodInfo()

        # append the generated method
        convert_class.lines[method_append_idx] += '\n' + from_grpc_m.build()

        # map convert method with the converter
        self.method_converter_dict[convert_method_name] = convert_class
        return True

    def _gen_from_grpc_list_field_code(self, from_grpc_m: JavaMethodBuilder, domain_f: JavaFieldInfo, grpc_f_type: str,
                                       convert_class: JavaClassInfo, import_append_idx: int,
                                       base_indent: int, has_lombok_builder: bool):
        domain_setter_s = f'domain.set{CommonUtils.upper_first(domain_f.name)}'
        if has_lombok_builder:
            domain_setter_s = f'.{domain_f.name}'
        grpc_getter = f'grpc.get{CommonUtils.upper_first(domain_f.name)}List()'

        if grpc_f_type in self.mapping.grpc.basic_proto_type_set:
            if has_lombok_builder:
                from_grpc_m.add_line(f'{domain_setter_s}({grpc_getter})', base_indent + 2)
            else:
                from_grpc_m.add_line(f'{domain_setter_s}({grpc_getter});', base_indent)
            return

        optional_line = f'{domain_setter_s}(Optional.ofNullable({grpc_getter}).orElse(Lists.newArrayList()).stream()'
        from_grpc_m.add_line(optional_line, base_indent)
        from_grpc_m.add_line('.filter(Objects::nonNull)', base_indent + 2)
        from_grpc_m.add_line(f'.map(grpcItem -> from{grpc_f_type}(grpcItem))', base_indent + 2)
        if has_lombok_builder:
            from_grpc_m.add_line('.collect(Collectors.toList()))', base_indent + 2)
        else:
            from_grpc_m.add_line('.collect(Collectors.toList()));', base_indent + 2)
        append_import_to_line(convert_class, java_utils_dict['Optional'], import_append_idx)
        append_import_to_line(convert_class, java_utils_dict['Collectors'], import_append_idx)

    def _new_converter_class(self, package: str, service: object) -> JavaClassInfo:
        new_converter = JavaClassInfo(False)
        new_converter.package = package
        new_converter.simple_name = service.module_name + self.converter_config.default_suffix
        new_converter.full_name = new_converter.package + '.' + new_converter.simple_name
        c_config = self.converter_config
        the_path = f'{c_config.project_path}/{c_config.root_path}/{c_config.converter_path}'
        the_path += f'/{new_converter.simple_name}.java'
        new_converter.path = the_path
        new_converter.lines.append(f'package {new_converter.package};\n')
        new_converter.lines.append('\n')
        new_converter.lines.append('import org.springframework.stereotype.Component;\n')
        new_converter.lines.append('\n')
        # converter class comment
        new_converter.lines.append('\n')
        new_converter.lines.append(JavaAstUtils.make_document_comment('Maycur Tool'))
        new_converter.lines.append('\n')
        new_converter.lines.append('@Component\n')
        new_converter.lines.append(f'public class {new_converter.simple_name}' + ' {\n')
        new_converter.lines.append('\n')
        new_converter.lines.append('}\n')
        return new_converter

    def _convert_list_argument_type(self, convert_class: JavaClassInfo,
                                    java_list_type: str, grpc_f: GrpcField) -> str:
        grpc_f_type = grpc_f.f_type.replace('repeated', '').strip()
        if java_list_type not in self.domain_loader.simple_name_dict:
            return grpc_f_type

        domain = self.domain_loader.simple_name_dict[java_list_type]
        grpc = self.mapping.grpc.struct_dict[grpc_f_type]
        self._convert_grpc_2_domain(convert_class, grpc, domain)
        self._convert_domain_2_grpc(convert_class, domain, grpc)
        return grpc_f_type

    def _do_convert_java_and_grpc_by_domain_field(self, domain_f: JavaFieldInfo):
        field_domain = self.domain_loader.simple_name_dict[domain_f.type]
        package_prefix = f'{self.converter_config.root_package}.{self.converter_config.domain_path}'
        if field_domain.package != package_prefix:
            target_folder = field_domain.package.replace(package_prefix + '.', '').split('.')[0]
            first_upper_folder = CommonUtils.upper_first(target_folder)
            field_converter_simple_name = f'{first_upper_folder}{self.converter_config.default_suffix}'
            field_converter_name = field_converter_simple_name + '.java'
        else:
            field_converter_name = f'{field_domain.simple_name}{self.converter_config.default_suffix}.java'
        converter_path = self.get_converter_path()
        checking_converter_path = re.sub('/+', '/', f'{converter_path}/{field_converter_name}')
        if os.path.exists(checking_converter_path):
            converter = self.converter_loader.simple_name_dict[field_converter_simple_name]
        else:
            raise RuntimeError(f'not support yet, the domain {domain_f.name}, type: {domain_f.type}')
        grpc_key = domain_f.type
        if grpc_key not in self.mapping.grpc.struct_dict:
            grpc_key = self.config.proto_config.message_prefix + domain_f.type
            if grpc_key not in self.mapping.grpc.struct_dict:
                raise AttributeError(f'gRPC message not found for domain type: {domain_f.type}')
        grpc = self.mapping.grpc.struct_dict[grpc_key]
        self._convert_grpc_2_domain(converter, grpc, field_domain)
        self._convert_domain_2_grpc(converter, field_domain, grpc)

    def _do_convert_java_and_grpc(self, converter_path: str):
        if len(self.mapping.ice.ice_2_grpc_services) > 0:
            for service_item in self.mapping.ice.ice_2_grpc_services:
                converter_simple_name = service_item.module_name + self.converter_config.default_suffix
                converter_name = converter_simple_name + '.java'
                checking_converter_path = re.sub('/+', '/', f'{converter_path}/{converter_name}')
                if os.path.exists(checking_converter_path):
                    converter = self.converter_loader.simple_name_dict[converter_simple_name]
                else:
                    lower_converter_path = self.converter_config.converter_path.lower().replace('/', '.')
                    converter_package = f'{self.converter_config.root_package}.{lower_converter_path}'
                    converter = self._new_converter_class(converter_package, service_item)
                if len(service_item.structs) > 0:
                    converter_changed = False
                    for item in service_item.structs:
                        # do convert only when gRPC and domain both exist
                        if item.name in self.domain_loader.simple_name_dict:
                            # item_package = self.converter_config.root_package+'.'+self.converter_config.domain_path
                            # domain_full_name = f'{item_package}.{item.name}'
                            # use fullname_dict TODO
                            domain = self.domain_loader.simple_name_dict[item.name]
                            converter_changed |= self._convert_grpc_2_domain(converter, item, domain)
                            converter_changed |= self._convert_domain_2_grpc(converter, domain, item)
                    if converter_changed:
                        self.converters.append(converter)
        if len(self.mapping.java.java_2_grpc_services) > 0:
            pass

    def _do_flush_disk(self):
        if self.config.test_mode:
            return
        if len(self.converters) > 0:
            for item in self.converters:
                if item.is_exist:
                    # update the converter
                    with open(item.path, 'w') as update_f:
                        update_f.writelines(item.lines)
                else:
                    # create the converter
                    pre_check_path = item.path[:item.path.rindex('/')]
                    if not os.path.exists(pre_check_path):
                        os.makedirs(pre_check_path)
                    with open(item.path, 'x') as create_f:
                        create_f.writelines(item.lines)

    def _get_root_path(self):
        return f'{self.converter_config.project_path}/{self.converter_config.root_path}'

    def get_converter_path(self):
        root_path = self._get_root_path()
        converter_path = f'{root_path}/{self.converter_config.converter_path}'
        return re.sub('/+', '/', converter_path)

    def convert_java_and_grpc(self):
        root_path = self._get_root_path()
        domain_path = f'{root_path}/{self.converter_config.domain_path}'
        converter_path = self.get_converter_path()
        domain_path = re.sub('/+', '/', domain_path)

        self._load_all_domains(domain_path)
        self._load_all_converters(converter_path)

        self._do_convert_java_and_grpc(converter_path)

        self._do_flush_disk()
